;=================================================
    .DEF GAME_DATA_INDEX_ENABLE,      0       ;是否使用节目数据 Bank 索引
    .DEF GAME_DATA_INDEX_BANK,        $3F     ;节目数据 Bank 索引 的 Bank 号
    .DEF GAME_DATA_INDEX_ADDR,        $A000   ;节目数据 Bank 索引 的 Bank 地址
;-------------------------------------------------
    .DEF GAME_DATA_BANK,              CUSTOM_DATA_BANK     ;最终切换数据所在Bank
    .DEF GAME_DATA_ADDR,              CUSTOM_DATA_ADDR    ;最终切换数据起始地址
    .DEF GAME_SELECT_PROC_ADDR,       $0400   ;切换节目地址
    .DEF CHR_RAM_WRITE_PROC_ADDR,     $0300   ;写入CHR-VRAM处理地址
    .DEF SELECT_CLEAR_ADDR,           $01E0   ;清除RAM代码地址
;=================================================
;卡带寄存器
    .DEF GAME_REG_0,                  $6000
    .DEF GAME_REG_1,                  GAME_REG_0 + 1
    .DEF GAME_REG_2,                  GAME_REG_1 + 1
    .DEF GAME_REG_3,                  GAME_REG_2 + 1
    .DEF GAME_REG_COUNT,              4

;=================================================

    .ENUM                  $20
        ;=================================================
        ;占用零页内存
        Game_Chr_Size,               1
        Game_Flags,                  1
        ;------------------------------------------------------------
        Game_Reg_0_For_Chr,          1
        Game_Reg_1_For_Chr,          1
        Game_Reg_2_For_Chr,          1
        Game_Reg_3_For_Chr,          1
        ;------------------------------------------------------------
        Game_Reg_0_For_Prg,          1
        Game_Reg_1_For_Prg,          1
        Game_Reg_2_For_Prg,          1
        Game_Reg_3_For_Prg,          1
        ;------------------------------------------------------------
        FC_Game_Data_Tmp,            1       ;临时缓冲数据
        FC_Game_Data_Count,          1       ;数据计数
        FC_Game_Word_L,              1       ;临时缓冲数据
        FC_Game_Word_H,              1       ;数据计数
        FC_Game_Data_L,              1       ;临时缓冲数据
        FC_Game_Data_H,              1       ;数据计数
    .ENDE
        ;------------------------------------------------------------
    .DEF FC_Game_Index_L,            FC_Item_Index_L ;节目索引低位
    .DEF FC_Game_Index_H,            FC_Item_Index_H ;节目索引高位

;========================================
;获取节目数据所在bank
Get_Game_Config_From_Bank_Index
    LDA #$00
    STA FC_Game_Data_L
    STA FC_Game_Data_H
    
    .IF GAME_DATA_INDEX_ENABLE
    
        ;切换到存放配置bank号数据所在的bank
        LDA #GAME_DATA_INDEX_BANK & BANK_DATA_MASK
        JSR Bank_Switch_A000

        ;取到bank
        LDA FC_Game_Index_L
        CLC
        ADC #<GAME_DATA_INDEX_ADDR
        STA FC_Game_Data_L
        LDA FC_Game_Index_H
        ADC #>GAME_DATA_INDEX_ADDR
        STA FC_Game_Data_H

    .ENDIF

    ;切换到节目数据所在bank
    LDY #$00
    LDA (FC_Game_Data_L),Y
    CLC
    ADC #GAME_DATA_BANK & BANK_DATA_MASK
    JSR Bank_Switch_A000

    ;得到起始
    LDA GAME_DATA_ADDR
    STA FC_Game_Word_L
    LDA GAME_DATA_ADDR + 1
    STA FC_Game_Word_H

    ;得到起始索引
    LDA FC_Game_Index_L
    SEC
    SBC FC_Game_Word_L
    STA FC_Game_Data_L
    LDA FC_Game_Index_H
    SBC FC_Game_Word_H
    STA FC_Game_Data_H

    ASL FC_Game_Data_L
    ROL FC_Game_Data_H

    ;得到配置数据起始地址
    LDA FC_Game_Data_L
    CLC
    ADC #<GAME_DATA_ADDR + 2
    STA FC_Game_Data_L
    LDA FC_Game_Data_H
    ADC #>GAME_DATA_ADDR
    STA FC_Game_Data_H
    RTS

;========================================
Get_Game_Config
    JSR Get_Game_Config_From_Bank_Index
    
    ;得到配置数据地址
    LDY #$00
    LDA (FC_Game_Data_L),Y
    PHA
    INY
    LDA (FC_Game_Data_L),Y
    STA FC_Game_Data_H
    PLA
    STA FC_Game_Data_L

    ;位组合标志
    LDY #$00
    LDA (FC_Game_Data_L),Y
    STA <Game_Flags

    ;设置命名表镜像
    LDA <Game_Flags
    AND #$01
    EOR #$01
    STA MAPPER_REG_MIRRORING

    ;CHR大小
    INY
    LDA (FC_Game_Data_L),Y
    STA Game_Chr_Size

    ;CHR 8KB偏移 -> 16KB偏移
    LSR Game_Reg_0_For_Chr
    ROR Game_Reg_1_For_Chr
    INY

    ;最终CHR寄存器配置
    LDX #$00
.Get_Game_Chr_Config
    LDA (FC_Game_Data_L),Y
    STA Game_Reg_0_For_Chr,X
    INY
    INX
    CPX #GAME_REG_COUNT
    BCC .Get_Game_Chr_Config
 
    ;最终PRG寄存器配置
    LDX #$00
.Get_Game_Prg_Config
    LDA (FC_Game_Data_L),Y
    STA Game_Reg_0_For_Prg,X
    INY
    INX
    CPX #GAME_REG_COUNT
    BCC .Get_Game_Prg_Config
    RTS

;==================================================
;选择节目处理
Select_Game_Proc
    LDX #$00
Select_Game_Proc.Rom_Select_Chr_Offset
    LDA Game_Reg_0_For_Chr,X
    STA GAME_REG_0,X
    INX
    CPX #GAME_REG_COUNT
    BCC Select_Game_Proc.Rom_Select_Chr_Offset

    LDA #$00
    STA <FC_Game_Data_Tmp
    STA <FC_Game_Data_Count

;拷贝 CHR 数据 到 CHR-RAM
Select_Game_Proc.Write_CHR_To_Vram
    LDA #$06
    STA MAPPER_REG_BANK_CTRL

    ;如果8KB偏移地址是偶数, 则CHR-ROM从0开始, 否则从1开始, 因为8KB CHR的 ROM会组合成16KB放在一起
    LDA <Game_Reg_3_For_Chr
    AND #@00001111
    CLC
    ADC <FC_Game_Data_Tmp
    STA MAPPER_REG_BANK_DATA

    LDA <FC_Game_Data_Count
    ASL
    ASL
    ASL
    TAY
Select_Game_Proc.Mapping_Vram_0000_0FFF;设置PPU地址 $0000-$07FF, MMC3 8000写入$00,$01
    LDX #$00
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA ;0000-07FF
    INX
    INY
    INY
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA ;0800-0FFF
    INY
Select_Game_Proc.Mapping_Vram_1000_1FFF;设置PPU地址 $1000-$1FFF, MMC3 8000写入$02,$03,$04,$05
    INX
    INY
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA ;1400-17FF
    CPX #$05
    BCC Select_Game_Proc.Mapping_Vram_1000_1FFF

    LDA #$80
    STA CHR_RAM_WRITE_PROC_ADDR + 2

    LDA <Game_Reg_3_For_Chr
    AND #@00010001
    BEQ Select_Game_Proc.Write_Game_Vram_From_8000

Select_Game_Proc.Write_Game_Vram_From_A000
    LDA #$A0
    STA CHR_RAM_WRITE_PROC_ADDR + 2
Select_Game_Proc.Write_Game_Vram_From_8000
    LDA Game_Chr_Size
    BEQ Select_Game_Proc.Rom_Prg_Bank_Init
    LDA PPU_STATUS
    LDY #$00
    STY PPU_ADDRESS
    STY PPU_ADDRESS
    
    LDX #$20
    JSR CHR_RAM_WRITE_PROC_ADDR

    INC FC_Game_Data_Tmp
    INC FC_Game_Data_Count
    LDA FC_Game_Data_Count
    CMP Game_Chr_Size
    BCC Select_Game_Proc.Write_CHR_To_Vram
    
Select_Game_Proc.Rom_Prg_Bank_Init;初始化 PRG Bank $8000-BFFF
    
    ;128KB耗费周期统计
    ;切页    2608: 163 * 16   = 0.087帧
    ;读写 1441792: (5 + 4 + 2) * 131072 = 48.40帧
    ;判断  393168: 3 * (131072 - 512) + 2 * 512 + 16 * 29 = 13.201帧
    ;粗略: 1837568: 61.698帧
    
    ;实测 128KB CHR 耗费周期1842515 61.85帧  1710611 57.435帧
    ;实测 256KB CHR 耗费周期3685027 123.73帧
    
    ;实测 128KB CHR 耗费周期: 1712083  57.48帧 之前 1842515  61.85帧, 节约4帧
    ;实测 128KB CHR 耗费周期: 3424163 114.97帧 之前 3685027 123.73帧, 节约9帧
    
    LDA #$06
    STA MAPPER_REG_BANK_CTRL
    LDA #$00
    STA MAPPER_REG_BANK_DATA
    LDA #$07
    STA MAPPER_REG_BANK_CTRL
    LDA #$01
    STA MAPPER_REG_BANK_DATA

Select_Game_Proc.Rom_Chr_Bank_Init_0000_0FFF;初始化 CHR Bank $0000-0FFF
    LDX #$00
    LDY #$00
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA
    INX
    LDY #$02
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA
    
    LDY #$04
Select_Game_Proc.Rom_Chr_Bank_Init_1000_1FFF;初始化 CHR Bank $1000-1FFF
    INX
    INY
    STX MAPPER_REG_BANK_CTRL
    STY MAPPER_REG_BANK_DATA
    CPX #$05
    BCC Select_Game_Proc.Rom_Chr_Bank_Init_1000_1FFF
 
Select_Game_Proc.Rom_Select_Prg_Offset
    LDX #$00
Select_Game_Proc.Rom_Select_Prg_Offset_Set
    LDA Game_Reg_0_For_Prg,X
    STA GAME_REG_0,X
    INX
    CPX #GAME_REG_COUNT
    BCC Select_Game_Proc.Rom_Select_Prg_Offset_Set

    ;启用 PRG RAM
    LDA #$80
    STA MAPPER_REG_PRG_RAM_PROTECT
    
    JMP SELECT_CLEAR_ADDR
Select_Game_Proc_End

;==================================================
;写入CHR-VRAM处理
Write_Vram_Proc
Write_Vram_Proc.Write_Vram_Proc_Write
    LDA $8000,Y
    STA $2007
    INY
    BNE Write_Vram_Proc.Write_Vram_Proc_Write
    INC CHR_RAM_WRITE_PROC_ADDR + 2
    DEX
    BNE Write_Vram_Proc.Write_Vram_Proc_Write
    RTS
Write_Vram_Proc_End

;==================================================
;选择节目后清理RAM处理
Clear_Ram_Proc
    LDX #$00
    TXA
Clear_Ram_Proc.Write_Zero
    STA $00,X
    STA $0200,X
    STA $0300,X
    STA $0400,X
    STA $0500,X
    STA $0600,X
    STA $0700,X
    INX
    BNE Clear_Ram_Proc.Write_Zero
    JMP ($FFFC)
Clear_Ram_Proc_End

;==================================================
;选择游戏
Select_Game
    LDA #$00
    STA PPU_CTRL
    LDA #$00
    STA PPU_MASK
Select_Game.Nmi_Wait
    LDA PPU_STATUS
    BPL Select_Game.Nmi_Wait

;清除调色板
Select_Game.Palette_Clear
    LDA #$3F
    STA $2006
    LDA #$00
    STA $2006
    LDY #$20
    LDA #$0F
Select_Game.Write_Pal_Data
    STA PPU_DATA
    DEY
    BNE Select_Game.Write_Pal_Data
    
;清理音频
Select_Game.Stop_Sound;停止音乐
    LDX #$00
    LDA #$00
Select_Game.Stop_Sound_Write
    STA $4000,X
    INX
    CPX #$14
    BCC Select_Game.Stop_Sound_Write
    STA APU_STATUS
    
;清理RAM
    LDX #$00
    TXA
Select_Game.Write_Zero
    STA $0100,X
    STA $0200,X
    STA $0300,X
    STA $0400,X
    STA $0500,X
    STA $0600,X
    STA $0700,X
    INX
    BNE Select_Game.Write_Zero
 
    ;拷贝选择节目的代码到内存
    LDX #$00
Select_Game.Select_GameCopy
    LDA Select_Game_Proc,x
    STA GAME_SELECT_PROC_ADDR,X
    INX
    CPX #Select_Game_Proc_End - Select_Game_Proc
    BCC Select_Game.Select_GameCopy
 
    ;清理切换节目端口
    LDA #$00
    LDX #$00
Select_Game.Game_Temp_Reg_Clear
    STA Game_Reg_0_For_Chr,X
    STA Game_Reg_0_For_Prg,X
    INX
    CPX #08
    BCC Select_Game.Game_Temp_Reg_Clear

    JSR Get_Game_Config

    ;拷贝选择节目后清理内存的代码到内存
    LDX #$00
Select_Game.Select_Game_Ram_Clear_Copy
    LDA Clear_Ram_Proc,x
    STA SELECT_CLEAR_ADDR,X
    INX
    CPX #Clear_Ram_Proc_End - Clear_Ram_Proc
    BCC Select_Game.Select_Game_Ram_Clear_Copy
    
    ;拷贝CHR-VRAM写入程序
    LDX #$00
Select_Game.Select_Vram_Proc_Copy
    LDA Write_Vram_Proc,x
    STA CHR_RAM_WRITE_PROC_ADDR,X
    INX
    CPX #Write_Vram_Proc_End - Write_Vram_Proc
    BCC Select_Game.Select_Vram_Proc_Copy
    
    LDX #$FF
    TXS
    JMP GAME_SELECT_PROC_ADDR

;==================================================
;菜单大小初始化
Menu_Size_Init
    LDX #$00
Menu_Size_Init.Write
    LDA Menu_Size_Init.Data,X
    STA GAME_REG_0,X
    INX
    CPX #GAME_REG_COUNT
    BCC Menu_Size_Init.Write
    RTS
Menu_Size_Init.Data
    ; $6000      ; $6001     ; $6002     ; $6003
    ; 7  bit  0  ; 7  bit  0 ; 7  bit  0 ; 7  bit  0
    ; ---- ----  ; ---- ---- ; ---- ---- ; ---- ----
    ; ABCC DEEE  ; GHIJ KKLx ; STTT MMMM ; NPZP QQRx

    ;ROM_Size_Mask:;容量大小掩码
             ;IHGB
    ;.DB @00000011 ; 128KB -> 6000 40 80
    ;.DB @00000010 ; 256KB -> 6000 00 80
    ;.DB @00000000 ; 512KB -> 6000 00 00
    ;.DB @00000000 ;
    ;.DB @00000100 ;1024KB -> 6000 00 40
    ;.DB @00000000 ;
    ;.DB @00000000 ;
    ;.DB @00000000 ;
    ;.DB @00001100 ;2048KB -> 6000 00 60
    ;CCKKJEEEQQR(A24-A14)

    .IF NES_16KB_PRG_SIZE == 8
    ;     ABCCDEEE
        .DB @11000011
    ;     GHIJKKLx
        .DB @10000000
    ;     STTTMMMM
        .DB @00000000
    ;     NPZPQQRx
        .DB @00000000
    .ENDIF
    
    .IF NES_16KB_PRG_SIZE == 16
    ;     ABCCDEEE
        .DB @10000010
    ;     GHIJKKLx
        .DB @10000000
    ;     STTTMMMM
        .DB @00000000
    ;     NPZPQQRx
        .DB @00000000
    .ENDIF
    
    .IF NES_16KB_PRG_SIZE == 32
    ;     ABCCDEEE
        .DB @10000000
    ;     GHIJKKLx
        .DB @00000000
    ;     STTTMMMM
        .DB @00000000
    ;     NPZPQQRx
        .DB @00000000
    .ENDIF
    